import { NS, Constants } from './base';

/**
 * 计算图形
 * @param root 节点树根节点
 * @param config 配置
 * @returns
 */
export function toCalcGraphData<XNode extends NS.XNode | NS.DrwNode = NS.XNode>(root: XNode, config: Partial<NS.Opts> = {}) {
  const opts = Object.assign({
    space: [70, 20], size: [100, 50], offset: [0, 0], arrow: 5,
    direction: Constants.GRAPH_DIRECTION_L2R, compact: false, debug: false,
    optimizeData: true
  }, config);

  const dnodes: Array<NS.DrwNode & XNode> = [];
  // 简化空节点
  if (opts.optimizeData) toSimplifyNodes(root.children || [], opts.compact);

  const offset = [0, 0] as [number, number];
  offset[0] += opts.offset[0] + opts.space[0];
  offset[1] += opts.offset[1] + opts.space[1];
  // 计算坐标
  const rootNode = toCalcNodePosition.call(root as NS.DrwNode, dnodes, offset, opts);

  const dsize: [number, number] = [0, 0];
  dsize[0] += rootNode.size[0] + offset[0] * 2;
  dsize[1] += rootNode.size[1] + offset[1] * 2;

  const dline: string = toCalcLine(rootNode, opts, dsize);
  const dlines =
    !opts.debug ? [dline] : dline.split('M').filter(d => d.trim()).map(d => `M${d}`);
  return { dnodes, dlines, dsize, root: rootNode }
}

/**
 * 计算节点坐标
 * @param this
 * @param offset 起始偏移量
 * @param opts 绘制对象
 * @returns
 */
function toCalcNodePosition(this: NS.DrwNode, dnodes: NS.DrwNode[], offset: [number, number], opts: NS.Opts) {
  this.position = [...offset];
  if (this.type === Constants.Node) {
    const [w, h] = opts.size;
    if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
      this.size = [w, h, w, 1, 1];
    } else {
      this.size = [w, h, h, 1, 1];
    }
    dnodes.push(this);
    return this;
  }
  this.size = [0, 0, 0, 0, 0];
  if (!this.children || this.children.length === 0) return this;

  let [x, y] = offset;
  // const size = [宽度,最高高度，第1层高度]
  if (this.type === Constants.Parallel) {
    let offset_space = 0;
    for (const child of this.children) {
      child.pname = this.name;
      toCalcNodePosition.call(child, dnodes, [x, y], opts);
      if (child.size[0] === 0) continue;
      offset_space = opts.direction === Constants.GRAPH_DIRECTION_T2B
        ? opts.space[0] : opts.space[1];

      // 盒子大小 宽度,最高高度，第1层高度
      if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
        this.size[0] += child.size[0] + offset_space;
        this.size[1] = Math.max(this.size[1], child.size[1]);
      } else {
        this.size[0] = Math.max(this.size[0], child.size[0]);
        this.size[1] += child.size[1] + offset_space;
      }

      this.size[2] += child.size[2] + offset_space;
      this.size[3] += child.size[3];
      this.size[4] += child.size[4];
      // y = Math.max(child.position[1] + offset_space_y, y);
      if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
        if (child.position[0] + offset_space > x) {
          this.position[0] = Math.max(offset[0], offset[0] + (child.position[0] - offset[0]) / 2)
        }
        x += child.size[2] + offset_space;
      } else {
        if (child.position[1] + offset_space > y) {
          this.position[1] = Math.max(offset[1], offset[1] + (child.position[1] - offset[1]) / 2)
        }
        y += child.size[2] + offset_space;
      }
      continue;
    }
    if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
      this.size[0] -= offset_space;
    } else {
      this.size[1] -= offset_space;
    }
    this.size[2] -= offset_space;
    return this;
  }

  if (this.type === Constants.Serial) {
    let max_pos = 0;
    let offset_space = 0;
    for (const child of this.children) {
      child.pname = this.name;
      toCalcNodePosition.call(child, dnodes, [x, y], opts);
      if (child.size[0] === 0) continue;
      offset_space = opts.direction === Constants.GRAPH_DIRECTION_T2B
        ? opts.space[1] : opts.space[0];

      // 盒子大小 宽度,最高高度，第1层高度
      if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
        this.size[0] = Math.max(this.size[0], child.size[0]);
        this.size[1] += child.size[1] + offset_space;
      } else {
        this.size[0] += child.size[0] + offset_space;
        this.size[1] = Math.max(this.size[1], child.size[1]);
      }

      this.size[2] = Math.max(this.size[2], child.size[2]);
      // 坐标
      if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
        max_pos = Math.max(max_pos, child.position[0])
        y += child.size[1] + offset_space;
      } else {
        x += child.size[0] + offset_space;
        max_pos = Math.max(max_pos, child.position[1])
      }

      if (this.size[3] === 0) this.size[3] = child.size[3];
      this.size[4] = child.size[4];
      // y0 = Math.max(y0, child.position[1]);
      continue;
    }

    if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
      this.size[1] -= offset_space;
    } else {
      this.size[0] -= offset_space;
    }
    /** 修正坐标 */
    if (max_pos <= 0) return this;

    // 坐标是0:X, 1:Y
    const pos = opts.direction === Constants.GRAPH_DIRECTION_T2B ? 0 : 1;

    this.position[pos] = max_pos;
    for (const child of this.children) {
      const offset = max_pos - child.position[pos];
      let offset_x = opts.direction === Constants.GRAPH_DIRECTION_T2B ? offset : 0;
      let offset_y = opts.direction === Constants.GRAPH_DIRECTION_T2B ? 0 : offset;
      if (offset > 0) toFixOffsetCalcNodePosition.call(child, offset_x, offset_y);
    }
    return this;
  }
  return this;
}


/**
 * 递归修正Y轴偏移量
 * @param this 节点
 * @param offset_x X轴偏移量
 * @param offset_y Y轴偏移量
 */
function toFixOffsetCalcNodePosition(this: NS.DrwNode, offset_x: number, offset_y: number) {
  this.position[0] += offset_x;
  this.position[1] += offset_y;
  for (const child of this.children) {
    toFixOffsetCalcNodePosition.call(child, offset_x, offset_y);
  }
}



/**
 * 计算节点线
 * @param this
 * @param opts
 * @param graphSize 图形大小
 * @returns
 */
export function toCalcLine(dnode: NS.DrwNode, opts: NS.Opts, graphSize: [number, number]): string {
  let line = Constants.GRAPH_DIRECTION_T2B === opts.direction
    ? toCalcLineT2B.call(dnode, opts, graphSize)
    : toCalcLineL2R.call(dnode, opts, graphSize);
  line += toCalcLeanLine(dnode, opts);
  return line;
}


function toCalcLineL2R(this: NS.DrwNode, opts: NS.Opts, graphSize: [number, number]): string {
  let line = '';
  // 画箭头
  if (this.type === Constants.Node && this.pname) {
    let [x, y] = this.position;
    // 起始点小于偏移开始节点, 无需画箭头
    if (x <= opts.offset[0] + opts.space[0]) return line;
    const arrow = opts.arrow;
    if (arrow <= 0) return line;
    y += this.size[1] / 2;
    line += `M${x - arrow},${y - arrow} L${x - 1},${y} L${x - arrow},${y + arrow} `;
    return line;
  }

  if (!this.children || this.children.length === 0) return line;

  let childNext: NS.DrwNode;
  let i = 0;
  for (const child of this.children) {
    // 空盒子
    if (child.size[0] === 0) continue;
    // 递归画子节点线
    line += toCalcLine(child, opts, graphSize);
    // 弟弟节点
    childNext = this.children[++i];
    // 普通节点不用画线
    if (this.type === Constants.Node) continue;
    // 父级盒子与子级盒子一样, 无需画该子级本身的线条
    if (this.size[0] === child.size[0] && this.size[1] === child.size[1]) continue;

    // 并行情况
    if (this.type === Constants.Parallel) {
      // 画左边 -|
      const position = [...child.position];
      position[0] -= opts.space[0] / 2;
      position[1] += opts.size[1] / 2;
      let [x, y] = position;
      // 是否是多子节点

      // 是紧凑模型时或开始位置大于偏移位置
      if (opts.compact || x > opts.offset[0] + opts.space[0]) {

        const move = [x + opts.space[0] / 2, y].join(',');
        if (child.size[3] === 1) {
          // 1.移动画线坐标
          line += `M${move} `;
          // 2.画横线
          line += `L${position} `;

        } else {
          line += `M${position} `;
        }

        // 3.画竖线
        if (childNext && childNext.size[0] > 0) {
          const position = [...childNext.position];
          position[0] -= opts.space[0] / 2;
          position[1] += opts.size[1] / 2;
          line += `L${position} `;
        }
      }

      // 画右边 -|
      x += opts.space[0] / 2;

      // 推算下个节点X坐标 = 本级节点X(包含自身宽度) + 间距 +下个盒子宽度 + 图偏移量;
      const x_next = x + this.size[0] + opts.space[0] + opts.offset[0];
      if (opts.compact || graphSize[0] > x_next) {

        // 1.移动画线坐标
        const move = [x + child.size[0], y];
        if (child.size[4] !== 1) move[0] += opts.space[0] / 2;

        line += `M${move.join(',')} `;
        // 2.画横线
        line += `L${[x + this.size[0] + opts.space[0] / 2, y].join(',')} `;
        // 3.画竖线
        if (childNext) {
          const position = [...childNext.position];
          position[0] += this.size[0] + opts.space[0] / 2;
          position[1] += opts.size[1] / 2;
          line += `L${position} `;
        }
      }

      continue;
    }


    if (this.type === Constants.Serial && childNext) {
      // 相邻节点为并行节点-不画串行先
      // if (child.type === childNext.type && child.type === Constants.Parallel) continue;
      // 起始坐标 >= 结束坐标 不用划线(无效- 不应该存在)
      // if (child.position[0] >= childNext.position[0]) continue;
      let [x, y] = child.position;
      y += opts.size[1] / 2;
      x += child.size[0];
      // 是否是多个子节点
      if (child.size[4] !== 1) x += opts.space[0] / 2;
      const start = [x, y].join(',');
      x = childNext.position[0];
      // 是否是多个子节点
      if (childNext.size[3] !== 1) x -= opts.space[0] / 2;
      const end = [x, y].join(',');
      // console.log(this.name, child.name, childNext.name, `M${start} L${end} `)
      line += `M${start} L${end} `;
      continue;
    }
  }
  return line;
}

function toCalcLineT2B(this: NS.DrwNode, opts: NS.Opts, graphSize: [number, number]): string {
  let line = '';

  // 画箭头
  if (this.type === Constants.Node && this.pname) {
    let [x, y] = this.position;
    // 起始点小于偏移开始节点, 无需画箭头
    if (y <= opts.offset[1] + opts.space[1]) return line;
    const arrow = opts.arrow;
    if (arrow <= 0) return line;
    x += this.size[0] / 2;
    line += `M${x - arrow},${y - arrow} L${x},${y - 1} L${x + arrow},${y - arrow} `;
    return line;
  }

  if (!this.children || this.children.length === 0) return line;

  let childNext: NS.DrwNode;
  let i = 0;
  for (const child of this.children) {
    // 空盒子
    if (child.size[0] === 0) continue;
    // 递归画子节点线
    line += toCalcLine(child, opts, graphSize);
    // 弟弟节点
    childNext = this.children[++i];
    // 普通节点不用画线
    if (this.type === Constants.Node) continue;
    // 父级盒子与子级盒子一样, 无需画该子级本身的线条
    if (this.size[0] === child.size[0] && this.size[1] === child.size[1]) continue;

    // 并行情况
    if (this.type === Constants.Parallel) {
      // 画左边 -|
      const position = [...child.position];
      position[0] += opts.size[0] / 2;
      position[1] -= opts.space[1] / 2;
      let [x, y] = position;
      // 是否是多子节点

      // 是紧凑模型时或开始位置大于偏移位置
      if (opts.compact || y > opts.offset[1] + opts.space[1]) {

        const move = [x, y + opts.space[1] / 2].join(',');
        if (child.size[3] === 1) {
          // 1.移动画线坐标
          line += `M${move} `;
          // 2.画横线
          line += `L${position} `;
        } else {
          line += `M${position} `;
        }


        // 3.画竖线
        if (childNext && childNext.size[0] > 0) {
          const position = [...childNext.position];
          position[0] += opts.size[0] / 2;
          position[1] -= opts.space[1] / 2;
          line += `L${position} `;
        }
      }


      // 画右边 -|
      y += opts.space[1] / 2;

      // 推算下个节点X坐标 = 本级节点X(包含自身宽度) + 间距 +下个盒子宽度 + 图偏移量;
      const y_next = y + this.size[1] + opts.space[1] + opts.offset[1];
      if (opts.compact || graphSize[1] > y_next) {
        // 1.移动画线坐标
        const move = [x, y + child.size[1]];
        if (child.size[4] !== 1) move[1] += opts.space[1] / 2;

        line += `M${move.join(',')} `;
        // 2.画横线
        line += `L${[x, y + this.size[1] + opts.space[1] / 2].join(',')} `;
        // 3.画竖线
        if (childNext) {
          const position = [...childNext.position];
          position[1] += this.size[1] + opts.space[1] / 2;
          position[0] += opts.size[0] / 2;
          line += `L${position} `;
        }
      }

      continue;
    }


    if (this.type === Constants.Serial && childNext) {
      // 相邻节点为并行节点-不画串行先
      // if (child.type === childNext.type && child.type === Constants.Parallel) continue;
      // 起始坐标 >= 结束坐标 不用划线(无效- 不应该存在)
      // if (child.position[0] >= childNext.position[0]) continue;
      let [x, y] = child.position;
      x += opts.size[0] / 2;
      y += child.size[1];
      // 是否是多个子节点
      if (child.size[4] !== 1) y += opts.space[1] / 2;
      const start = [x, y].join(',');
      y = childNext.position[1];
      // 是否是多个子节点
      if (childNext.size[3] !== 1) y -= opts.space[1] / 2;
      const end = [x, y].join(',');

      // console.log(this.name, child.name, childNext.name, `M${start} L${end} `)
      line += `M${start} L${end} `;
      continue;
    }
  }
  return line;
}


/**
 * 添加依赖先
 * @param dnodes 节点
 * @param opts 配置
 * @returns 依赖线条
 */
export function toCalcLeanLine(node: NS.DrwNode, opts: NS.Opts) {
  const dnodes = toExtractNodes(node);
  const map: Record<string, NS.DrwNode> = {};
  const leans: [string, string][] = [];
  let leanline = '';
  for (const dnode of dnodes) {
    if (dnode.type !== Constants.Node) continue;
    map[dnode.name] = dnode;
    if (typeof dnode.lean !== 'string') continue;
    const lean = dnode.lean.replace(/\s/g, '');
    if (lean.length === 0) continue;
    const leannames = lean.split(',');
    for (const leanname of leannames) {
      leans.push([leanname, dnode.name]);
    }
  }

  if (leans.length === 0) return leanline;
  for (const lean of leans) {
    // 没有找到依赖的节点
    if (!map[lean[0]]) continue;
    const n1 = map[lean[0]]; // 开始节点
    const n2 = map[lean[1]]; // 结束节点

    if (opts.direction === Constants.GRAPH_DIRECTION_T2B) {
      // 从上到下
      let [x, y] = n1.position;
      x += n1.size[0] / 2;
      y += n1.size[1];
      const start = [x, y].join(',');

      [x, y] = n2.position;
      x += n2.size[0] / 2;
      const end = [x, y].join(',');
      leanline += `M${start} L${end} `;
    } else {
      // 从左到右
      let [x, y] = n1.position;
      x += n1.size[0];
      y += n1.size[1] / 2;
      const start = [x, y].join(',');

      [x, y] = n2.position;
      y += n2.size[1] / 2;
      const end = [x, y].join(',');

      leanline += `M${start} L${end} `;
    }
  }

  return leanline;
}

/**
 * 简化节点
 * @param nodes 节点列表
 * @returns 节点列表
 */
export function toSimplifyNodes(nodes: NS.XNode[], compact = false): NS.XNode[] {
  if (nodes.length === 0) return nodes;
  const array = nodes.splice(0);
  let node: NS.XNode | undefined;
  for (node of array) {
    node = toSimplifyNode.call(node, compact);
    if (node) nodes.push(node);
  }
  return nodes;
}
function toSimplifyNode(this: NS.XNode, compact = false): NS.XNode | undefined {
  if (this.type === Constants.Node) return this;
  if (!this.children || this.children.length === 0) return undefined;
  if (this.children.length === 1) return toSimplifyNode.call(this.children[0]);
  for (let i = 0; i < this.children.length; i++) {
    let child = this.children[i];
    if (child.type === Constants.Node) continue;
    const rchild = toSimplifyNode.call(child);
    if (rchild !== child || this.type === child.type) {
      if (!child.children || child.children.length === 0) {
        this.children.splice(i, 1);
        i--;
        continue;
      }
      this.children.splice(i, 1, ...child.children);
      i--;
      continue;
    }
  }
  return this;
}

function toExtractNodes(dnode: NS.DrwNode, dnodes: NS.DrwNode[] = []) {
  if (dnode.type === Constants.Node) {
    dnodes.push(dnode);
    return dnodes;
  }
  for (const child of dnode.children) {
    toExtractNodes(child, dnodes);
  }
  return dnodes;
}





